﻿using System;
using System.Linq;
using System.Xml;
using Notoric.Model.Elements;
using Notoric.Model.Internal;
using System.Collections.Generic;

namespace Notoric.Model.Packaging
{
    /// <summary>
    /// Implements an XML package part that holds the data of a composition model element (<see cref="CompositionElement"/>).
    /// </summary>
    public sealed class CompositionPart : XmlPackagePart
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="CompositionPart"/> instance.
        /// </summary>
        /// <param name="package">
        /// <para>
        /// Type: <see cref="Package"/>
        /// </para>
        /// <para>
        /// The parent package (the package that owns this part).
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="ProjectPart"/>
        /// </para>
        /// <para>
        /// The project package part that is the logical owner of this part.
        /// This must not be <see langword="null"/>.
        /// </para>
        /// </param>
        /// <param name="implementationPart">
        /// <para>
        /// Type: <see cref="System.IO.Packaging.PackagePart"/>
        /// </para>
        /// <para>
        /// An existing part of the parent's <see cref="Notoric.Model.Packaging.Package.ImplementationPackage"/> that implements
        /// the core functionality of this part.
        /// </para>
        /// </param>
        /// <param name="createNew">
        /// <para>
        /// Type: <see cref="System.Boolean"/>
        /// </para>
        /// <para>
        /// Indicates whether the content of this part should be created from scratch or loaded from the part content.<br/>
        /// <see langword="true"/> means create a new part content, <see langword="false"/> means load the content from
        /// the physical storage.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="package"/>, <paramref name="owner"/> or <paramref name="implementationPart"/> is <see langword="null"/>.
        /// </exception>
        internal CompositionPart(Package package, ProjectPart owner, System.IO.Packaging.PackagePart implementationPart, bool createNew)
            : base(package, owner, implementationPart, createNew)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            Composition.Parts.CollectionChanged += new EventHandler<Collections.NotifyCollectionChangedEventArgs>(Parts_CollectionChanged);
            Composition.Measures.CollectionChanged += new EventHandler<Collections.NotifyCollectionChangedEventArgs>(Measures_CollectionChanged);
        }

        #endregion

        #region XmlPackagePart Members

        /// <inheritdoc/>
        protected override ModelElement CreateAndLoadRoot(XmlElement element)
        {
            return new CompositionElement(this, element);
        }

        /// <inheritdoc/>
        protected override ModelElement CreateNewRoot()
        {
            return new CompositionElement(this);
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when the collection of composition's measures has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void Measures_CollectionChanged(object sender, Collections.NotifyCollectionChangedEventArgs e)
        {
            if (LinearView != null)
            {
                switch (e.Action)
                {
                    case Collections.NotifyCollectionChangedAction.Add:
                        if (e.NewStartingIndex == 0)
                            linearView.MusicSection.FirstPresentedMeasure = Composition[1];
                        linearView.MusicSection.LastPresentedMeasure = Composition[(ushort)Composition.MeasuresCount];
                        break;
                    case Collections.NotifyCollectionChangedAction.Remove:
                        if (e.OldStartingIndex == 0)
                            linearView.MusicSection.FirstPresentedMeasure = (Composition.MeasuresCount > 0 ? Composition[1] : null);
                        linearView.MusicSection.LastPresentedMeasure = (Composition.MeasuresCount > 0 ? Composition[(ushort)Composition.MeasuresCount] : null);
                        break;
                    case Collections.NotifyCollectionChangedAction.Move:
                        if (e.OldStartingIndex == 0 || e.NewStartingIndex == 0)
                            linearView.MusicSection.FirstPresentedMeasure = Composition[1];
                        if (e.OldStartingIndex == Composition.MeasuresCount - 1 || e.NewStartingIndex == Composition.MeasuresCount - 1)
                            linearView.MusicSection.LastPresentedMeasure = Composition[(ushort)Composition.MeasuresCount];
                        break;
                    case Collections.NotifyCollectionChangedAction.Replace:
                        if (e.OldStartingIndex == 0)
                            linearView.MusicSection.FirstPresentedMeasure = Composition[1];
                        else if (e.OldStartingIndex == Composition.MeasuresCount - 1)
                            linearView.MusicSection.LastPresentedMeasure = Composition[(ushort)Composition.MeasuresCount];
                        break;
                    case Collections.NotifyCollectionChangedAction.Reset:
                        linearView.MusicSection.FirstPresentedMeasure = null;
                        linearView.MusicSection.LastPresentedMeasure = null;
                        break;
                }
            }
        }

        /// <summary>
        /// Occurs when the collection of composition's parts has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void Parts_CollectionChanged(object sender, Collections.NotifyCollectionChangedEventArgs e)
        {
            if (LinearView != null)
            {
                switch (e.Action)
                {
                    case Collections.NotifyCollectionChangedAction.Add:
                        {
                            int index = e.NewStartingIndex;

                            foreach (PartDescriptorElement part in e.NewItems)
                                linearView.MusicSection.Parts.Insert(index++, part);
                            break;
                        }
                    case Collections.NotifyCollectionChangedAction.Remove:
                        foreach (PartDescriptorElement part in e.OldItems)
                            linearView.MusicSection.Parts.Remove(part);
                        break;
                    case Collections.NotifyCollectionChangedAction.Move:
                        linearView.MusicSection.Parts.Move(e.OldStartingIndex, e.NewStartingIndex);
                        break;
                    case Collections.NotifyCollectionChangedAction.Replace:
                        {
                            foreach (PartDescriptorElement part in e.OldItems)
                                linearView.MusicSection.Parts.Remove(part);

                            int index = e.NewStartingIndex;

                            foreach (PartDescriptorElement part in e.NewItems)
                                linearView.MusicSection.Parts.Insert(index++, part);
                            break;
                        }
                    case Collections.NotifyCollectionChangedAction.Reset:
                        linearView.MusicSection.Parts.Clear();
                        break;
                }
            }
        }

        #endregion

        #region Events

        /// <summary>
        /// Occurs when a change to the composition's music data is pending
        /// that will affect other package parts.
        /// </summary>
        /// <remarks>
        /// <para>
        /// When this event is raised, the change has not yet been committed.
        /// Before committing the change the affected package parts are deserialized
        /// and loaded into memory (if they were not already active) so that the change
        /// to the music data is observed by all the altered objects.
        /// </para>
        /// </remarks>
        public event EventHandler<GlobalChangePendingEventArgs> GlobalChangePending;

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates the linear view of this composition.
        /// </summary>
        /// <param name="interiorCreator">
        /// <para>
        /// Type: <see cref="SectionInteriorCreator"/>
        /// </para>
        /// <para>
        /// The factory method that will create the interior of the linear view's music section.
        /// </para>
        /// </param>
        /// <param name="parameter">
        /// <para>
        /// Type: <see cref="Object"/>
        /// </para>
        /// <para>
        /// An implementation defined initialization parameter for the music section's interior.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="interiorCreator"/> is <see langword="null"/>.
        /// </exception>
        public void CreateLinearView(SectionInteriorCreator interiorCreator, object parameter)
        {
            linearView = Package.CreateLinearCompositionViewPart(this, interiorCreator);

            foreach (PartDescriptorElement part in Composition.Parts)
                linearView.MusicSection.Parts.Add(part);
        }

        /// <summary>
        /// Raises the <see cref="GlobalChangePending"/> event and loads the affected package parts
        /// into the memory.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The event is raised if and only if there is a <see cref="MusicSectionElement"/> that references
        /// this composition and presents a measure in the range [<paramref name="firstAffectedMeasure"/>, <paramref name="lastAffectedMeasure"/>].
        /// </para>
        /// <para>
        /// No argument can be <see langword="null"/>. If only one measure is altered by the change, set both parameters
        /// to the same value.
        /// </para>
        /// </remarks>
        /// <param name="firstAffectedMeasure">
        /// <para>
        /// Type: <see cref="MeasureElement"/>
        /// </para>
        /// <para>
        /// The first measure affected by the pending change.
        /// </para>
        /// </param>
        /// <param name="lastAffectedMeasure">
        /// <para>
        /// Type: <see cref="MeasureElement"/>
        /// </para>
        /// <para>
        /// The last measure affected by the pending change.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="firstAffectedMeasure"/> or the <paramref name="lastAffectedMeasure"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The <paramref name="firstAffectedMeasure"/> or the <paramref name="lastAffectedMeasure"/> is not a part of this composition.<br/>
        /// -or-<br/>
        /// The number of the <paramref name="firstAffectedMeasure"/> is greater than the numbe of the <paramref name="lastAffectedMeasure"/>.
        /// </exception>
        /// <exception cref="SerializationException">
        /// An error occured while loading the affected package parts.
        /// </exception>
        public void RaiseGlobalChangePending(MeasureElement firstAffectedMeasure, MeasureElement lastAffectedMeasure)
        {
            if (firstAffectedMeasure == null)
                throw new ArgumentNullException("firstAffectedMeasure");
            if (lastAffectedMeasure == null)
                throw new ArgumentNullException("lastAffectedMeasure");

            var affectedParts = Composition.GetReferencingPages(firstAffectedMeasure, lastAffectedMeasure);

            if (GlobalChangePending != null)
            {
                var eventData = new GlobalChangePendingEventArgs();
                foreach (var reference in affectedParts)
                    eventData.AffectedParts.Add(reference.GetPageVirtualPath());
            }

            foreach (var part in affectedParts)
                part.Load();

            if (linearView != null && linearView.RootElement == null)
                throw new SerializationException("Failed to load the linear view!", "");
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the root <see cref="CompositionElement"/> of the part
        /// (Overrides the <see cref="XmlPackagePart.RootElement"/>).
        /// </summary>
        public CompositionElement Composition
        {
            get { return (CompositionElement)RootElement; }
        }

        /// <summary>
        /// Gets the linear view of this composition.
        /// </summary>
        public LinearCompositionViewPart LinearView
        {
            get
            {
                if (linearView == null)
                {
                    /*var rsCollection = ImplementationPart.GetRelationshipsByType(PackageVocabulary.LinearCompositionViewCompositionRelationshipType);
                    var lvRS = rsCollection.FirstOrDefault();
                    if (lvRS != null)
                        linearView = (LinearCompositionViewPart)Package.GetPart(lvRS.TargetUri);*/
                    var uriString = Uri.ToString();
                    uriString = uriString.Substring(0, uriString.LastIndexOf('/'));
                    uriString += PackageVocabulary.LinearCompositionViewPartUri;
                    var uri = new Uri(uriString, UriKind.Relative);
                    if (Package.PartExists(uri))
                        linearView = (LinearCompositionViewPart)Package.GetPart(uri);
                }
                return linearView;
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="LinearView"/> property.
        /// </summary>
        private LinearCompositionViewPart linearView;

        #endregion
    }

    /// <summary>
    /// Holds the data of a <see cref="CompositionPart.GlobalChangePending"/> event.
    /// </summary>
    public class GlobalChangePendingEventArgs : EventArgs
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="GlobalChangePendingEventArgs"/> instance.
        /// </summary>
        public GlobalChangePendingEventArgs()
        {
            AffectedParts = new List<String>();
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the list of virtual paths to parts that will
        /// be affected by the pending change.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="List{T}"/> of <see cref="String"/>
        /// </para>
        /// <para>
        /// The list of virtual paths to parts that will
        /// be affected by the pending change.
        /// </para>
        /// </value>
        public List<String> AffectedParts
        {
            get;
            private set;
        }

        #endregion
    }
}